/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-04
 * V4.0
 */
package com.jphenix.kernel.classloader;

import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.ClassInfo;

import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.Manifest;

/**
 * 用于热替换的类加载器
 * 
 * 2019-01-16 增加了可以加载扩展类路径的方法，并修改了错误
 * 2019-02-11 没干啥，在一个地方加了日志后，又发现不能在那里加日志，又把日志注释掉了
 * 2019-10-30 增加了加载扩展库中readme.txt中描述的引用其它扩展库信息
 * 2019-11-04 修改了，扩展类路径中引用了其它扩展类路径时无效的错误
 * 2020-05-12 方法名addNewSource改为addSource
 * 
 * @author 刘虻
 * 2008-4-10下午05:07:58
 */
@ClassInfo({"2020-05-24 21:58","用于热替换的类加载器"})
public class BeanClassLoader extends URLClassLoader {

	protected ResourceService   rs      = null;   //包资源服务

	/**
	 * 构造函数
	 * @param parent 父类加载器
	 * 2008-4-10下午05:07:58
	 */
	public BeanClassLoader(ClassLoader parent,ResourceService rs) {
		super(new URL[0],parent);
		this.rs = rs;
	}
	
	/**
	 * 执行加载/WEB-INF/lib 子文件夹中的jar包和zip包
	 * @param libPath /WEB-INF/lib 的全路径
	 * @return 加载的子文件夹（可以在程序中用来判断是否加载了哪个子文件夹）
	 * 2018年3月30日
	 * @author MBG
	 */
	public List<String> loadLibChildPath(String libPath) {
		//搜索的文件信息序列
		List<String> fileList = new ArrayList<String>();
		try {
			SFilesUtil.getFileList(fileList,libPath,null,"jar;zip",true,true);
		}catch(Exception e) {
			e.printStackTrace();
			return new ArrayList<String>();
		}
		//构建返回值
		List<String> reList = new ArrayList<String>();
		int point; //分隔符位置
		String pathKey; //文件所在的路径
		for(String subPath:fileList) {
			point = subPath.lastIndexOf("/");
			if(point<0) {
				//这不是子文件夹中的文件
				continue;
			}
			pathKey = subPath.substring(0,point);
			if(!reList.contains(pathKey)) {
				reList.add(pathKey);
			}
			rs.addClassPath(libPath+"/"+subPath,null); //加载该包文件
		}
		return reList;
	}
	
	
	/**
	 * 加载扩展库文件
	 * @param extLibs   扩展库相对路径数组（相对于 WEB-INF/ext_lib）
	 * @param basePath  根路径
	 * @return 加载的扩展类路径（相对于 WEB-INF/ext_lib）
	 * 2019年1月18日
	 * @author MBG
	 */
	public List<String> loadExtLibs(String[] extLibs,String basePath) {
		if(extLibs==null || extLibs.length<1) {
			return new ArrayList<String>();
		}
		//已经加载的库文件序列（避免重复加载）
		List<String> jarList = new ArrayList<String>();
		//构建返回值
		List<String> res     = new ArrayList<String>();
		
		List<String> pathList; //搜索到的路径
		String       objPath;     //需要搜索的路径
		for(int i=0;i<extLibs.length;i++) {
			if(res.contains(extLibs[i])) {
				continue;
			}
			res.add(extLibs[i]);
			
			pathList  = new ArrayList<String>();
			try {
				if(basePath==null) {
					objPath = extLibs[i];
				}else {
					objPath = SFilesUtil.getAllFilePath(extLibs[i],basePath);
				}
				
				//检查当前扩展库文件夹中是否存在readme.txt文件，文件中是否引用了其它扩展库
				loadImportExtLibs(objPath,basePath,res,jarList);
				
				//执行搜索项目库文件夹
				SFilesUtil.getFileList(pathList,objPath,null,"jar;zip",true,false);
				
				for(String path:pathList) {
					if(jarList.contains(path)) {
						continue;
					}
					jarList.add(path);
					rs.addClassPath(path,extLibs[i]); //加载该包文件
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		return res;
	}
	
	
	/**
	 * 加载扩展库文件
	 * @param extLibPath 库文件相对路径（多个用分号分隔）（相对于 WEB-INF/ext_lib）
	 * @param basePath    根路径
	 * 2019年1月15日
	 * @author MBG
	 */
	public void loadExtLibs(String extLibPath,String basePath) {
		if(extLibPath==null || extLibPath.length()<1) {
			return;
		}
		//已经加载的库文件序列（避免重复加载）
		List<String> jarList     = new ArrayList<String>();
		//已经加载的扩展库路径序列
		List<String> extPathList = new ArrayList<String>();
		
		//用分隔符分隔为多个子路径
		String[] paths = BaseUtil.split(extLibPath,":","：",";","；",",","，");
		List<String> pathList; //搜索到的路径
		String       objPath;     //需要搜索的路径
		for(int i=0;i<paths.length;i++) {
			pathList  = new ArrayList<String>();
			try {
				if(basePath==null) {
					objPath = paths[i];
				}else {
					objPath = SFilesUtil.getAllFilePath(paths[i],basePath);
				}
				if(extPathList.contains(objPath)) {
					continue;
				}
				extPathList.add(objPath);
				
				//检查当前扩展库文件夹中是否存在readme.txt文件，文件中是否引用了其它扩展库
				loadImportExtLibs(objPath,basePath,extPathList,jarList);
				
				//执行搜索项目库文件夹
				SFilesUtil.getFileList(pathList,objPath,null,"jar;zip",true,false);
				
				for(String path:pathList) {
					if(jarList.contains(path)) {
						continue;
					}
					jarList.add(path);
					rs.addClassPath(path,paths[i]); //加载该包文件
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 检查当前扩展库文件夹中是否存在readme.txt文件，文件中是否引用了其它扩展库
	 * @param libPath     指定扩展库绝对路径
	 * @param basePath    扩展库根路径
	 * @param extPathList 已经加载的扩展库路径（防止循环加载，死循环）
	 * @param jarList     已经加载的扩展库文件路径序列（防止重复加载）
	 * 2019年10月30日
	 * @author MBG
	 */
	private void loadImportExtLibs(
			 String       libPath
			,String       basePath
			,List<String> extPathList
			,List<String> jarList) {
		//检查扩展库中是否存在readme.txt文件（注意：文件名全部小写）
		File readmeFile = new File(libPath+"/readme.txt");
		if(!readmeFile.exists()) {
			return;
		}
		List<String> cntList = null; //文件内容
		String[]     paths   = null; //用分隔符分隔为多个子路径
		List<String> pathList;       //搜索到的路径
		String       objPath;        //需要搜索的路径
		try {
			//获取文件内容
			cntList = BaseUtil.splitToList(FileCopyTools.copyToString(readmeFile,"UTF-8"),"\r\n","\n");
		}catch(Exception e) {
			e.printStackTrace();
			return;
		}
		if(cntList==null || cntList.size()<1) {
			return;
		}
		for(String ele:cntList) {
			ele = ele.trim();
			if(ele.startsWith("#") || ele.length()<1) {
				continue;
			}
			paths = BaseUtil.split(ele,":","：",";","；",",","，");
			for(int i=0;i<paths.length;i++) {
				if(extPathList.contains(paths[i])) {
					continue;
				}
				extPathList.add(paths[i]);
				pathList  = new ArrayList<String>();
				try {
					if(basePath==null) {
						objPath = paths[i];
					}else {
						objPath = SFilesUtil.getAllFilePath(paths[i],basePath);
					}
					//检查当前扩展库文件夹中是否存在readme.txt文件，文件中是否引用了其它扩展库
					loadImportExtLibs(objPath,basePath,extPathList,jarList);
					
					//执行搜索项目库文件夹
					SFilesUtil.getFileList(pathList,objPath,null,"jar;zip",true,false);
					
					for(String path:pathList) {
						if(jarList.contains(path)) {
							continue;
						}
						jarList.add(path);
						rs.addClassPath(path,paths[i]); //加载该包文件
					}
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 加载类到缓存
	 * 刘虻
	 * 2010-8-31 下午01:47:29
	 * @param name 类名
	 * @param re 类资源对象
	 * @throws ClassNotFoundException 执行发生异常
	 */
	protected void createClass(String name,ResourceEntry re) throws ClassNotFoundException {
		InputStream is = null; //类读入流
		if(re.inJar) {
			//压缩文件对象
			File fileObj = new File(re.codeBase);
			re.lastModified = fileObj.lastModified();
			String childPath = re.source;
			if(childPath!=null) {
				//从压缩包全路径中分离出子路径
				int point = childPath.indexOf("!");
				if(point>0) {
					childPath = childPath.substring(point+1);
				}
			}
			try {
				is = SFilesUtil.getZipFileInputStreamByUrl(childPath,SFilesUtil.getZipFile(fileObj));
			}catch(Exception e) {
				e.printStackTrace();
				throw new ClassNotFoundException(e.toString());
			}
		}else {
			//构建资源文件对象
			File resFile = new File(re.source);
			re.lastModified = resFile.lastModified();
			try {
				is = new FileInputStream(resFile);
			}catch(Exception e) {
				e.printStackTrace();
				throw new ClassNotFoundException(e.toString());
			}
		}
		//输出流
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024]; //缓存
		int bytesRead = -1; //读取数
		try {
			while ((bytesRead = is.read(buffer)) != -1) {
				bos.write(buffer, 0, bytesRead);
			}
		}catch(Exception e) {
			e.printStackTrace();
			throw new ClassNotFoundException(e.toString());
		}finally {
			try {
				is.close();
			}catch (IOException ex) {}
		}
		try {
	        //初始化类
			re.loadedClass = 
				defineClass(name, bos.toByteArray(), 0,bos.size());
		}catch(Exception e) {
			e.printStackTrace();
			throw new ClassNotFoundException(e.toString());
		}
		//构建包对象
		String packageName = name.substring(0,name.lastIndexOf("."));
		if(getPackage(packageName)==null) {
			super.definePackage(packageName,new Manifest(),null);
		}
	}
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:54:04
	 */
	@Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
    	//通过类路径获取对应的相对路径
    	String classKey  = BaseUtil.swapString(name,".","/")+".class";
    	ResourceEntry re = rs.getSource(classKey); //资源对象
    	if(re==null) {
    		throw new ClassNotFoundException(name);
    	}
    	if(re.loadedClass==null) {
    		createClass(name,re);
    	}else {
	    	//检测是否发生变化
	    	File cTimeFile;
	    	if(re.inJar) {
	    		cTimeFile = new File(re.codeBase);
	    	}else{
	    		cTimeFile = new File(re.source);
	    	}
	    	if(re.lastModified!=cTimeFile.lastModified()) {
	    		//文件发生了变化
	    		createClass(name,re);
	    	}
    	}
    	return re.loadedClass;
    }
	
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-9-3 下午07:07:47
	 */
	@Override
    public URL getResource(String name) {
		URL reUrl = findResource(name);
		if(reUrl==null) {
			return super.getResource(name);
		}
		return reUrl;
	}
	
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:52:24
	 */
    @Override
    public URL findResource(final String name) {
		//整理路径
		String fixName = BaseUtil.swapString(name,"\\","/");
		ResourceEntry re = rs.getSource(fixName); //从全局包资源管理服务中获取资源对象
		if(re==null) {
			//不要在这里打日志，因为很多时候是尝试性寻找，不一定非得找到
			//System.err.println("findResource Not Find The Name:["+name+"]  fixName:["+fixName+"]");
			return null;
		}
		try {
			return re.toUrl();
		}catch(Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:51:47
	 */
    @Override
    public URL[] getURLs() {
    	return rs.getURLs();
    }
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2010-8-30 下午09:50:45
	 */
	@Override
    protected void addURL(URL url) {
    	if(url==null) {
    		return;
    	}
    	if("file".equalsIgnoreCase(url.getProtocol())) {
    		//该类只能处理本地文件
    		rs.addClassPath(url.getPath(),null);
    	}else {
    		super.addURL(url);
    	}
    }
	
	
	/**
	 * 添加一个类路径（注意：不支持多个路径用分隔符分割）
	 * @param classPath 一个类路径
	 * 2019年1月18日
	 * @author MBG
	 */
	public void addClassPath(String classPath) {
		rs.addClassPath(classPath,null);
	}
	
	/**
	 * 添加一个资源对象
	 * @param basePath    资源根路径
	 * @param sourceFile  资源文件对象
	 * 2019年1月18日
	 * @author MBG
	 */
	public void addSource(String basePath,File sourceFile) {
		rs.addSource(basePath,sourceFile,null);
	}
	
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-9-10上午10:00:21
	 */
	@Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
	    if(name==null || name.length()<1) {
	        throw new ClassNotFoundException("The ClassPath Is Null");
	    }
		try {
			return findClass(name);
		}catch(Exception e) {}
		
// 如果该类加载器是一个子类加载器，只负责新构建的类。如果通过这个类加载器加载父类加载器管理的类
// 在第一步findClass方法，就会抛异常，执行到此步
// 如果在此步用findSystemClass获取到的类是一个新的类，而并不是正在使用的类，从而导致类型不匹配出错
//		try {
//			//先判断是否为系统类
//			return super.findSystemClass(name);
//		}catch(Exception e) {}
		try {
			//先判断是否为系统类
			return super.loadClass(name);
		}catch(Exception e) {}
		try {
			//先判断是否为系统类
			return super.findSystemClass(name);
		}catch(Exception e) {}
		try {
			//在从本地加载器中获取
			return super.loadClass(name);
		}catch(Exception e) {}
		//获取类路径
		URL[] urls = getURLs();
		//构建报错信息
		StringBuffer sbf = new StringBuffer();
		sbf
		.append("\n\nNot Find The ClassPath:["+name+"] ClassLoader:"+this)
		.append("\n==============================URL List ================================== Count:[\"+urls.length+\"]\n");
		if(urls!=null) {
			for(int i=0;i<urls.length;i++) {
				sbf.append(urls[i]).append("\n");
			}
		}
		sbf.append("\n==============================URL List===================================\n");
		
		sbf.append(DebugUtil.getStackTraceString(this));
		throw new ClassNotFoundException("\nNot Find The ClassPath:["+name+"] "+sbf);
	}
}
